package com.jiazhong.Dao;

import com.alibaba.druid.pool.DruidDataSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 数据库的工具类
 * <p>
 * 1.连接数据库方法
 * 2.释放资源的方法
 * 3.执行增、删、改的方法
 */
public abstract class DBUtils {
    protected Connection conn = null;
    protected PreparedStatement ps = null;
    protected ResultSet rs = null;
    protected static DruidDataSource dataSource=null;
    /**
     * 静态代码块，在类加载时被执行，只会被执行一次
     * 读取db.properties配置文件中的数据并初始化数据库连接池
     */
    static {
        try {
            //1.读取db.properties属性文件中的数据，并加载到properties对象中
            //获得输入流对象
            InputStream inputStream = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
            //创建properties对象，用于存储db.properties中的数据
            Properties properties=new Properties();
            //将db.properties中的数据加载到properties对象中
            properties.load(inputStream);

            //2.创建Druid数据库连接池对象
            dataSource = new DruidDataSource();
            //指定数据源读取属性中的数据
            dataSource.configFromPropety(properties);



        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获得数据库连接的方法
     *
     * @return
     */
    protected Connection getConn() throws ClassNotFoundException, SQLException {
//        System.out.println("使用数据库连接池获取对象");
        conn=dataSource.getConnection();
        return conn;
    }

    protected Connection getConn1() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/stu_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai", "root", "root");
        return conn;
    }

    /**
     * 释放资源
     */
    protected void closeAll() {
        try {
            if (rs != null) {
                rs.close();
            }
            if (ps != null) {
                ps.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            conn = null;
            ps = null;
            rs = null;
        }
    }

    /**
     * 执行增删改方法
     *
     * @param sql    带有?号占位符的SQL语句
     * @param params SQL语句中?号占位符的值,约束(可变参数的值的顺序必须和?号的顺序一致)
     * @return
     */
    protected int executeUpdate(String sql, Object... params) {

        try {
            //连接数据库
            this.getConn();
            //创建预处理对象
            ps = conn.prepareStatement(sql);
            //判断SQL语句中是否包含?号占位符(params有值表示有？号占位符，有一个值表示有一个？，有多个值表示有多个？)
            if (params != null && params.length != 0) {
                //循环设置？号占位符的
                for (int i = 0; i < params.length; i++) {
                    ps.setObject(i + 1, params[i]);
                }
            }
            //执行SQL语句
            return ps.executeUpdate();
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {//释放资源
            this.closeAll();
        }
        return 0;
    }

    /**
     * 通用查询，查询表中的多条数据（每条数据对应一条Bean对象）
     * 该查询的结果自动封装到Bean对象中，并将Bean对象添加到List集合里
     * 封装的对象以及对象中的属性与表和查询的列有关
     *
     * @param cla    要封装的类的字节码对象，该类与要查询的表有关，SQL语句中要查询的表对应该类的字节码对象
     * @param sql    SQL语句
     * @param params SQL语句中？占位符的值
     * @param <T>    类型参数，通用泛型表示要封装的类
     * @return 封装好存有查询结果的List集合
     */
    public <T> List<T> executeQueryList(Class<T> cla, String sql, Object... params) {
        try {
            getConn();
            ps = conn.prepareStatement(sql);
            if (params != null && params.length != 0) {
                for (int i = 0; i < params.length; i++) {
                    ps.setObject(i + 1, params[i]);
                }
            }
            rs = ps.executeQuery();
            //创建一个用于存放所有对象的list集合
            List<T> list = new ArrayList<>();
            //获得查询结果集的元数据对象（元数据对象是描述结果集的对象<结果集的表结构>）
            ResultSetMetaData metaData = rs.getMetaData();//表结构
            //通过元数据对象获得查询“结果集”中的列数
            int columnCount = metaData.getColumnCount();
            //循环设置
            while (rs.next()) {
                //创建要封装的对象
                T t = cla.getConstructor().newInstance();
                //循环获得每一列数据，并将获得的列数据封装到对象的属性中
                for (int i = 0; i < columnCount; i++) {
                    //获得列名（列名和属性名一样）
                    String fieldName = metaData.getColumnName(i + 1);
                    //根据列名获得方法名
                    String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                    //根据列名获取对应的属性对象
                    Field field = cla.getDeclaredField(fieldName);
                    //根据方法名和参数类型获取方法对象
                    Method method = cla.getDeclaredMethod(methodName, field.getType());

                    //获得一列数据，并将改数据转换为指定类型
                    Object value = rs.getObject(i + 1, field.getType());

                    //调用方法,为t赋值
                    method.invoke(t, value);
//                   method.invoke(t, ConvertUtils.convert(value,field.getType()));
                }
                //将t对象添加到list集合中
                list.add(t);
            }
            return list;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通用查询，查询表中的单条数据（每条数据对应一条Bean对象）
     * 该查询的结果自动封装到Bean对象中，并将Bean对象返回
     * 封装的对象以及对象中的属性与表和查询的列有关
     *
     * @param cla    要封装的类的字节码对象，该类与要查询的表有关，SQL语句中要查询的表对应该类的字节码对象
     * @param sql    SQL语句
     * @param params SQL语句中？占位符的值
     * @param <T>    类型参数，通用泛型表示要封装的类
     * @return 存有查询结果的Bean对象
     */
    public <T> T executeQueryOne(Class<T> cla, String sql, Object... params) {
        try {
            getConn();
            ps = conn.prepareStatement(sql);
            if (params != null && params.length != 0) {
                for (int i = 0; i < params.length; i++) {
                    ps.setObject(i + 1, params[i]);
                }
            }
            rs = ps.executeQuery();

            //获得查询结果集的元数据对象（元数据对象是描述结果集的对象<结果集的表结构>）
            ResultSetMetaData metaData = rs.getMetaData();//表结构
            //通过元数据对象获得查询“结果集”中的列数
            int columnCount = metaData.getColumnCount();
            //循环设置
            if (rs.next()) {
                //创建要封装的对象
                T t = cla.getConstructor().newInstance();
                //循环获得每一列数据，并将获得的列数据封装到对象的属性中
                for (int i = 1; i < columnCount; i++) {
                    //获得列名（列名和属性名一样）
                    String fieldName = metaData.getColumnName(i + 1);
                    //根据列名获得方法名
                    String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                    //根据列名获取对应的属性对象
                    Field field = cla.getDeclaredField(fieldName);
                    //根据方法名和参数类型获取方法对象
                    Method method = cla.getDeclaredMethod(methodName, field.getType());

                    //获得一列数据，并将改数据转换为指定类型
                    Object value = rs.getObject(i + 1, field.getType());

                    //调用方法,为t赋值
                    method.invoke(t, value);
//                   method.invoke(t, ConvertUtils.convert(value,field.getType()));
                }
                if (rs.next()) {
                    throw new Exception("查询的是多行数据，但实际获得的是多行数据");
                }
                //将t对象添加到list集合中
                return t;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 通用查询，统计查询
     *
     * @param sql    SQL语句
     * @param params SQL语句中？占位符的值
     * @return 存有查询结果的Bean对象
     */
    public Long executeQueryCount(String sql, Object... params) {
        try {
            getConn();
            ps = conn.prepareStatement(sql);
            if (params != null && params.length != 0) {
                for (int i = 0; i < params.length; i++) {
                    ps.setObject(i + 1, params[i]);
                }
            }
            rs = ps.executeQuery();
            if (rs.next()) {
                return Long.valueOf(rs.getInt(1));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0L;
    }
}
